﻿namespace EFS.Common.Async
{
	/// <summary>
	/// Async delegate
	/// </summary>
	public class AsyncDelegate<T> where T : EFS.Common.ISelfDescriptor
	{
		/// <summary>
		/// Exception
		/// </summary>
		public class NotIdleDelegateException : System.Exception
		{
			public NotIdleDelegateException()
				: base("Can not register non-idle delegate")
			{ }
		}

		#region Composition

		/// <summary>
		/// Synk access
		/// </summary>
		private object syncAccess = new object();

		/// <summary>
		/// delegate
		/// </summary>
		public delegate void Delegate(T t);

		/// <summary>
		/// Threading mode
		/// </summary>
		public enum Threading
		{
			Pooled,
			Worker,
			STA,
		}

		/// <summary>
		/// Status for delegate
		/// </summary>
		public enum ActivityStatus
		{
 			Idle,
			Started,
			Terminated
		}

		/// <summary>
		/// status
		/// </summary>
		private ActivityStatus status;

		/// <summary>
		/// Registrar
		/// </summary>
		private Delegate delegateRegistrar = default(Delegate);

		// T
		private T t = default(T);

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		public AsyncDelegate( T t)
		{
			// root & work can not be null
			EFS.Common.Verify.Reference(t, "t");

			// make wait event
			this.t = t;
		}

		#endregion

		#region Usage

		/// <summary>
		/// Get status
		/// </summary>
		public ActivityStatus Status
		{
			get { return status; }
		}

		/// <summary>
		/// Register delegate
		/// </summary>
		public void Register(Delegate d)
		{
			lock (syncAccess)
			{
				if (status != ActivityStatus.Idle)
				{
					throw new NotIdleDelegateException();
				}

				// register
				delegateRegistrar += d;
			}
		}

		/// <summary>
		/// Unregister delegate
		/// </summary>
		public void UnRegister(Delegate d)
		{
			lock (syncAccess)
			{
				// only unregister while idle
				if (status == ActivityStatus.Idle)
				{
					// register
					delegateRegistrar -= d;
				}
			}
		}

		/// <summary>
		/// Queue this root for async operation
		/// </summary>
		/// <param name="userCallBack"></param>
		/// <returns></returns>
		public void Start(Threading threading = Threading.Pooled)
		{
			// log
			// ~ commented for performance ~ EFS.EventLog.Log.LogInfo("BeginAsyncProcess", this);
			lock (syncAccess)
			{
				// check
				if (status == ActivityStatus.Terminated || status == ActivityStatus.Started)
				{
					return;
				}

				// started
				status = ActivityStatus.Started;
			}

			try
			{


				// starttype
				switch (threading)
				{
#if !SILVERLIGHT
					case Threading.STA:
						{
							// start STA thread
							System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ThreadProc));
							thread.SetApartmentState(System.Threading.ApartmentState.STA);
							thread.Start(this);
							break;
						}
#endif
					case Threading.Worker:
						{
							// start non-STA thread
							System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ThreadProc));
							thread.Start(this);
							break;
						}
					default:
						{
							// make pooled thread
							System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ThreadProc), this);
							break;
						}
				}
			}
#if !SILVERLIGHT
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, t);
#else
			catch
			{
#endif
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Thread procedure callse up implementation
		/// </summary>
		/// <param name="obj"></param>
		private static void ThreadProc(System.Object obj)
		{
			try
			{
				// state
				AsyncDelegate<T> asyncDelegate = (AsyncDelegate<T>)obj;

				// check
				if (null != asyncDelegate.delegateRegistrar)
				{
					try
					{

						// check and run
						asyncDelegate.delegateRegistrar(asyncDelegate.t);
					}
#if !SILVERLIGHT
					catch (System.Exception ex)
					{
						// log
						EFS.EventLog.Log.LogException(ex, asyncDelegate.t);
#else
					catch
					{
#endif		
					}
				}

				// set status
				lock (asyncDelegate.syncAccess)
				{
					// clean-up
					asyncDelegate.delegateRegistrar = null;
					asyncDelegate.t = default(T);

					// terminated
					asyncDelegate.status = ActivityStatus.Terminated;
				}
			}
#if !SILVERLIGHT
			catch (System.Exception ex)
			{
				// must handle exception here
				EFS.EventLog.Log.LogException(ex);
#else
			catch
			{	
#endif
			}
		}

		#endregion
	}
}